--------------Heavy Barrel-------------
A 4am crack                  2017-08-12
-------------------. updated 2017-08-21
                   |___________________

Name: Heavy Barrel
Genre: arcade
Year: 1989
Credits: Artwork by S. Chastain,
  S. Tanaka, C. Robinson, Ironwind
  Software; programming by L. Feddersen
Publisher: Data East USA, Inc.
Platform: Apple //e or later (128K)
Media: single-sided 5.25-inch floppy
OS: Quick-DOS

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy reboots endlessly

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing immediately suspicious

Disk Fixer
  T00 -> looks like ProDOS bootloader
         and ProDOS disk catalog

Why didn't any of my copies work?
  Probably a nibble check in the
  startup program

Next steps:

  1. Search for the error condition
     (maybe we'll get lucky!)
  2. Disable protection check that
     leads to the error condition
  3. Declare victory

(*) go to the gym

                   ~

               Chapter 1
        Jump, Jump For My Love


As with many protection checks, this
disk reboots as soon as it fails. There
are many ways to reboot an Apple II,
but the simplest is a direct jump to
$C600, the slot 6 firmware. (The more
"correct" way is to jump to $FAA6,
which does a "cold" restart and scans
your slots from 7 down to 1 looking for
card firmware to run. Since I have a
hard drive device in slot 7, I can tell
this protection check is not doing
that, because it reboots slot 6 when it
fails.)

Anyway...

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      "4C 00 C6"

One match on track $00, sector $0E.

Turning to my trusty Disk Fixer sector
editor, I notice an oddity right out of
the gate: the boot sector has a #$03 in
byte 0, which tells the drive firmware
to load 3 sectors from track 0 before
transferring control to $0801.

                 --v--

-------------- DISK EDIT --------------
TRACK $00/SECTOR $00/VOLUME $FE/BYTE$00
---------------------------------------
$00:>03<4C 00 0A EA D0 27 78   CL@JjP'8
$08: AD 83 C0 AD 83 C0 A5 2B   -.@-.@%+
$10: 4A 4A 4A 4A 09 C0 85 3F   JJJJI@.?
$18: 8D FD FF A9 5C 85 3E A9   .}.)\.>)
$20: 00 8D FC FF 18 AD BF 08   @.|.X-?H
...
---------------------------------------
BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL

                 --^--

The boot sector (T00,S00) is loaded at
$0800. If byte 0 is more than 1, the
drive firmware will load additional
sectors at $0900, $0A00, and so on.
Theoretically it could load all 16
sectors on track 0, but very few disks
do this because it's generally faster
to write a little loop to load only the
sectors you need into the memory pages
you need them.

Also, the bytes at $0801 are "4C 00 0A"
a.k.a. "JMP $0A00". So we're loading 3
sectors and jumping to the last one.
Due to sector skewing, the last one is
actually T00,S0E. (The second one is
T00,S07. I know, right? There's a table
in "Beneath Apple DOS" on p. 3-23 that
explains sector skewing.)

Which is exactly where I found the
"JMP $C600".

Thus...

                 --v--

T00,S0E (loaded at $0A00)
----------- DISASSEMBLY MODE ----------
0000:EA             NOP
0001:EA             NOP
0002:A2 60          LDX   #$60

; initialize Death Counter (2 bytes)
0004:A9 56          LDA   #$56
0006:85 FD          STA   $FD
0008:A9 08          LDA   #$08
000A:C6 FC          DEC   $FC
000C:D0 04          BNE   $0012
000E:C6 FD          DEC   $FD

; If the Death Counter hits zero, that
; would be bad. Which part of "Death
; Counter" sounded good to you, anyway?
; (Specifically, this jumps to the
; immediate reboot we saw earlier.)
0010:F0 38          BEQ   $004A

; look for an #$FB nibble
0012:BC 8C C0       LDY   $C08C,X
0015:10 FB          BPL   $0012
0017:C0 FB          CPY   #$FB
0019:D0 ED          BNE   $0008

; kill a few cycles (not pointless,
; because the disk spins independently
; of the CPU, so all of these low-level
; disk reads are highly time-sensitive)
001B:F0 00          BEQ   $001D
001D:EA             NOP
001E:EA             NOP

; read data latch (note: no BPL loop
; here, we're just reading it once)
001F:BC 8C C0       LDY   $C08C,X

; do a compare to set or clear the
; carry bit (among other things, but
; it's the carry bit we care about)
0022:C0 08          CPY   #$08

; rotate the carry into the low bit of
; the accumulator
0024:2A             ROL

; if we just rolled a "1" bit out of
; the high bit of the accumulator, take
; this branch
0025:B0 0B          BCS   $0032

; next nibble needs to be #$FF
0027:BC 8C C0       LDY   $C08C,X
002A:10 FB          BPL   $0027
002C:C0 FF          CPY   #$FF

; ...otherwise we start over
002E:D0 D8          BNE   $0008

; loop back to get next nibble
0030:F0 EB          BEQ   $001D

; execution continues here (from $0A25)
; and we get another (full) nibble
0032:BC 8C C0       LDY   $C08C,X
0035:10 FB          BPL   $0032

; stash it in zero page
0037:84 FC          STY   $FC

; if the accumulator is anything but
; %00001010, start over
0039:C9 0A          CMP   #$0A
003B:D0 CB          BNE   $0008

; get one more nibble
003D:BD 8C C0       LDA   $C08C,X
0040:10 FB          BPL   $003D

; AND it with the previously stashed
; nibble, to get a single 4-and-4
; encoded byte value
0042:38             SEC
0043:2A             ROL
0044:25 FC          AND   $FC
0046:49 FF          EOR   #$FF

; branch on success
0048:F0 03          BEQ   $004D

; otherwise reboot
004A:4C 00 C6       JMP   $C600

I got lost several times trying to
follow this routine. I think the
easiest way to explain it is to show
the difference between the original
disk and my non-working copy.

                   ~

               Chapter 2
        It's Time To Get Visual


Here is the original disk, as seen by
the Copy II+ nibble editor. Nibbles
with extra "0" bits (timing bits) after
them are displayed in inverse on an
original machine, marked here with a
"+" after the nibble.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1A8E  LENGTH: 1813

1BE0: FE AB DE BD 96 96 96 96   VIEW
1BE8: 96 96 96 96 96 96 96 96
1BF0: 96 96 96 96 96 96 96 96
1BF8: 96 EA B6 FF DE AA EB FB
1C00: BF FD+BB+FB+FF FF+FF FF+ <-1C03
1C08: FF+FF+FF+D5 AA 96 FF FE
1C10: AA AA AA AB FF FF DE AA
1C18: EB FF+FF+FF+FF+FF+FF+D5  FIND:
1C20: AA AD A7 E9 DD 96 B6 F4  FB FF

                 --^--

It's easy to understand why a simple
sector copy failed. The sequence that
this code is looking for starts at
offset $1C03, which is between the end
of one sector and the beginning of the
next. (The data epilogue is at $1BFC;
the next address prologue is at $1C0B.)
Sector copiers discard everything
between those delimiters and rebuild
the track with a default pattern of
sync bytes. That pattern doesn't
include an $FB nibble, so the nibble
check fails.

But the EDD bit copy also failed. Here
is the original disk's pattern at
offset $1C03:

  - #$FB + timing bit
  - #$FF
  - #$FF + timing bit
  - #$FF
  - #$FF + timing bit

And here is what the same part of the
track looks like on my failed EDD copy:

                 --v--

1C00: BF FD+BB+FB+FF FF FF+FF+
1C08: FF+FF+FF+D5 AA 96 FF FE
1C10: AA AA AA AB FF FF DE AA
1C18: EB FF+FF+FF+FF+FF+FF+D5
1C20: AA AD A7 E9 DD 96 B6 F4

                 --^--

A subtle difference! The sequence at
offset $1C03 now looks like this:

  - #$FB + timing bit
  - #$FF
  - #$FF
  - #$FF + timing bit
  - #$FF + timing bit

This code is looking for #$FF nibbles
with an alternating pattern of timing
bit, no timing bit, timing bit, no
timing bit. It doesn't find that on the
bit copy, so it knows it's not running
on an original disk.

                   ~

               Chapter 3
       One Byte To Rule Them All


Continuing with the disassembly of
track $00 sector $0E (loaded at $0A00):

                 --v--

; success path is here -- reset zero
; page addresses to their normal values
004D:A9 60          LDA   #$60
004F:85 2B          STA   $2B
0051:A9 09          LDA   #$09
0053:85 27          STA   $27

; restore the boot sector code to its
; normal values
0055:A9 01          LDA   #$01
0057:8D 00 08       STA   $0800
005A:A9 A5          LDA   #$A5
005C:8D 01 08       STA   $0801
005F:A9 27          LDA   #$27
0061:8D 02 08       STA   $0802
0064:A9 C9          LDA   #$C9
0066:8D 03 08       STA   $0803
0069:A9 09          LDA   #$09
006B:8D 04 08       STA   $0804

; and continue with the normal boot
006E:4C 01 08       JMP   $0801

                 --^--

This entire routine is a kludge. The
bootloader was supposed to load only
one sector into $0800, then go right
into disk-specific code that loads the
RWTS in higher memory. Then they go and
plop this copy protection routine on
top, jump to it, do the nasty, and
restore the real bootloader one byte at
a time on the way out.

Anyway, the easiest patch is to jump
directly to the success path at $0A4D.

T00,S00,$02: 00 -> 4D

Quod erat liberandum.

                   ~

            Acknowledgments


Thanks to Ian Baronofsky for lending me
the original disk at Kansasfest 2017.

                   ~

               Changelog


2015-08-21

- typos (thanks LoGo)

2017-08-12

- initial release

---------------------------------------
A 4am crack                    No. 1359
------------------EOF------------------
